/* * Copyright (C) 2014 ParanoidAndroid Project. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.reindeercrafts.notificationpeek.peek; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.preference.PreferenceManager; import android.util.Log; import com.reindeercrafts.notificationpeek.settings.PreferenceKeys; public class SensorActivityHandler { public final static String ACTION_UPDATE_SENSOR_USE = "NotificationPeek.update_sensor_use"; private final static String TAG = "NotificationPeek.SensorActivityHandler"; private final static int INCREMENTS_TO_DISABLE = 5; private final static float NOISE_THRESHOLD = 0.5f; // Minimum proximity detected distance from object. Some devices use 0/1 to indicate // "near"/"far", others use actual values. private static final float MIN_PROX_DISTANCE = 3.0f; private SensorManager mSensorManager; private SensorEventListener mProximityEventListener; private SensorEventListener mGyroscopeEventListener; private Sensor mProximityLightSensor; private Sensor mGyroscopeSensor; private ScreenReceiver mScreenReceiver; private SensorChangedCallback mCallback; private Context mContext; private float mLastX = 0, mLastY = 0, mLastZ = 0; private int mSensorIncrement = 0; private boolean mWaitingForMovement; private boolean mHasInitialValues; private boolean mScreenReceiverRegistered; private boolean mProximityRegistered; private boolean mGyroscopeRegistered; private boolean mInPocket; private boolean mOnTable; // User preferences of using sensors or not. private boolean mUseGyroSensor; private boolean mUseProxLightSensor; public SensorActivityHandler(Context context, SensorChangedCallback callback) { mContext = context; mCallback = callback; mScreenReceiver = new ScreenReceiver(); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); mUseProxLightSensor = preferences.getBoolean(PreferenceKeys.PREF_PROX_LIGHT_SENSOR, true); mUseGyroSensor = preferences.getBoolean(PreferenceKeys.PREF_GYRO_SENSOR, true); initSensors(context); } private void initSensors(Context context) { mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); initProximityLightSensor(); initGyroscopeSensor(); } private void initGyroscopeSensor() { // get gyroscope sensor for on-table detection if (mUseGyroSensor) { mGyroscopeSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); } if (mGyroscopeSensor != null) { mGyroscopeEventListener = new SensorEventListener() { @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } @Override public void onSensorChanged(SensorEvent event) { float x = event.values[0]; float y = event.values[1]; float z = event.values[1]; boolean storeValues = false; if (mHasInitialValues) { float dX = Math.abs(mLastX - x); float dY = Math.abs(mLastY - y); float dZ = Math.abs(mLastZ - z); if (dX >= NOISE_THRESHOLD || dY >= NOISE_THRESHOLD || dZ >= NOISE_THRESHOLD) { if (mWaitingForMovement) { if (NotificationPeek.DEBUG) { Log.d(TAG, "On table: false"); } mOnTable = false; // If proximity/light sensor is not used, set mInPocket to the same // as mOnTable to synchronize status. if (!mUseProxLightSensor) { mInPocket = mOnTable; } mCallback.onTableModeChanged(mOnTable); registerEventListeners(); mWaitingForMovement = false; mSensorIncrement = 0; } storeValues = true; } else { if (mSensorIncrement < INCREMENTS_TO_DISABLE) { mSensorIncrement++; if (mSensorIncrement == INCREMENTS_TO_DISABLE) { unregisterProximityLightEvent(); if (NotificationPeek.DEBUG) { Log.d(TAG, "On table: true"); } mOnTable = true; if (!mUseProxLightSensor) { mInPocket = mOnTable; } mCallback.onTableModeChanged(mOnTable); mWaitingForMovement = true; } } } } if (!mHasInitialValues || storeValues) { mHasInitialValues = true; mLastX = x; mLastY = y; mLastZ = z; } } }; } else { // no accelerometer? time to buy a nexus } } private void initProximityLightSensor() { // get proximity sensor for in-pocket detection, if no proximity sensor detected, try // light sensor. if (mUseProxLightSensor) { mProximityLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY) != null ? mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY) : mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); } if (mProximityLightSensor != null) { mProximityEventListener = new SensorEventListener() { @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } @Override public void onSensorChanged(SensorEvent event) { boolean inPocket = event.values[0] == 0 || event.values[0] <= MIN_PROX_DISTANCE; if (inPocket) { if (mUseGyroSensor) { mOnTable = false; // we can't have phone on table and pocket at the same time } unregisterGyroscopeEvent(); } else { // Only register gyroscope sensor listener if user chooses to use it. if (!mGyroscopeRegistered && mUseGyroSensor) { registerEventListeners(); } } if (NotificationPeek.DEBUG) { Log.d(TAG, "In pocket: " + inPocket + ", old: " + mInPocket); } boolean oldInPocket = mInPocket; mInPocket = inPocket; if (!mUseGyroSensor) { mOnTable = mInPocket; } if (oldInPocket != inPocket) { mCallback.onPocketModeChanged(mInPocket); } } }; } else { // ugh, that's bad, run now that you can. } } public void updateUseSensors() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext); boolean useGyro = preferences.getBoolean(PreferenceKeys.PREF_GYRO_SENSOR, true); boolean useProxLight = preferences.getBoolean(PreferenceKeys.PREF_PROX_LIGHT_SENSOR, true); if (useGyro == mUseGyroSensor && useProxLight == mUseProxLightSensor) { // Same as current, return. return; } mUseGyroSensor = useGyro; mUseProxLightSensor = useProxLight; if (!mUseGyroSensor) { unregisterGyroscopeEvent(); mGyroscopeSensor = null; mOnTable = false; } else { initGyroscopeSensor(); } if (!mUseProxLightSensor) { unregisterProximityLightEvent(); mProximityLightSensor = null; mInPocket = false; } else { initProximityLightSensor(); } } public boolean isInPocket() { return mInPocket; } public boolean isOnTable() { return mOnTable; } public void registerScreenReceiver() { if (!mScreenReceiverRegistered) { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_SCREEN_ON); // Add intent filter for listening to sensor use changes. intentFilter.addAction(ACTION_UPDATE_SENSOR_USE); mContext.registerReceiver(mScreenReceiver, intentFilter); mScreenReceiverRegistered = true; } } public void unregisterScreenReceiver() { if (mScreenReceiverRegistered) { mContext.unregisterReceiver(mScreenReceiver); mScreenReceiverRegistered = false; } } public void registerEventListeners() { if (mProximityLightSensor != null && !mProximityRegistered && mUseProxLightSensor) { if (NotificationPeek.DEBUG) { Log.d(TAG, "Registering proximity polling"); } mSensorManager.registerListener(mProximityEventListener, mProximityLightSensor, SensorManager.SENSOR_DELAY_NORMAL); mProximityRegistered = true; } if (mGyroscopeSensor != null && !mGyroscopeRegistered && mUseGyroSensor) { if (NotificationPeek.DEBUG) { Log.d(TAG, "Registering gyroscope polling"); } mSensorManager.registerListener(mGyroscopeEventListener, mGyroscopeSensor, SensorManager.SENSOR_DELAY_NORMAL); mGyroscopeRegistered = true; } } public void unregisterEventListeners() { unregisterProximityLightEvent(); unregisterGyroscopeEvent(); } private void unregisterProximityLightEvent() { if (mProximityLightSensor != null && mProximityRegistered) { if (NotificationPeek.DEBUG) { Log.d(TAG, "Unregistering proximity polling"); } mSensorManager.unregisterListener(mProximityEventListener); mProximityRegistered = false; } } private void unregisterGyroscopeEvent() { if (mGyroscopeSensor != null && mGyroscopeRegistered) { if (NotificationPeek.DEBUG) { Log.d(TAG, "Unregistering gyroscope polling"); } mSensorManager.unregisterListener(mGyroscopeEventListener); mLastX = mLastY = mLastZ = 0; mSensorIncrement = 0; mGyroscopeRegistered = false; mHasInitialValues = false; } } public interface SensorChangedCallback { public abstract void onPocketModeChanged(boolean inPocket); public abstract void onTableModeChanged(boolean onTable); public abstract void onScreenStateChanged(boolean screenOn); } public class ScreenReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { mCallback.onScreenStateChanged(false); registerEventListeners(); } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { mCallback.onScreenStateChanged(true); unregisterEventListeners(); } else if (intent.getAction().equals(ACTION_UPDATE_SENSOR_USE)) { // Update sensor use preferences. Log.d(TAG, "Update sensor uses"); updateUseSensors(); } } } }